`include "IdealMemory.v"
`include "register_file.v"
`include "extender.v"
`include "ALUControl.v"
`include "ALU_behav.v"
`include "PipelinedControl.v"
`include "Hazard.v"

module PipelinedProc(CLK, Reset_L, startPC, dMemOut);

	input CLK;
	input Reset_L;
	input [31:0] startPC;
	output [31:0] dMemOut;

//============================================Wire Connections============================================//	
	//Hazard
	wire Bubble;
	wire PCWrite;
	wire IFWrite;
	
	//Stage 1
	wire	[31:0]	currentPCPlus4;
	wire	[31:0]	jumpDecisionTarget;
	wire	[31:0]	nextPC;
	reg		[31:0]	currentPC;
	wire	[31:0]	currentInstruction;
	
	//Stage 2
	reg		[31:0]	currentInstruction2;
	wire	[5:0]	opcode;
	wire	[4:0]	rs, rt, rd;
	wire	[15:0]	imm16;
	wire	[4:0]	shamt;
	wire	[5:0]	func;
	wire	[31:0]	busA, busB, ALUImmRegChoice, signExtImm;
	wire	[31:0]	jumpTarget;
	wire 			RegClear;
	
	//Stage 2 Control Wires
	wire	regDst, aluSrc, memToReg, regWrite, memRead, memWrite, branch, jump, signExtend;
	wire	UseShiftField;
	wire	rsUsed, rtUsed;
	wire	[4:0]	rw;
	wire	[3:0]	aluOp;
	wire	[31:0]	ALUBIn;
	wire	[31:0]	ALUAIn;
	wire	[31:0]	branchDst;
	
	//Stage 3
	reg		[31:0]	ALUAIn3, ALUBIn3, busB3, signExtImm3;
	reg		[4:0]	rw3;
	wire	[4:0]	rw4_temp;
	reg		[4:0]	rd3;
	reg		[4:0]	rt3;
	
	wire	[5:0]	func3;
	wire	[31:0]	shiftedSignExtImm;
	
	wire	[3:0]	aluCtrl;
	wire			aluZero;
	wire    [31:0]	aluOut;//*************
	wire 			Carry_in;
	wire 			Overflow; 
	wire 			Carry_out;
	
	//Stage 3 Control regs
	reg				regDst3, memToReg3, regWrite3, memRead3, memWrite3, branch3;
	reg	[3:0]		aluOp3;
	reg	[5:0]		opcode3;
	
	//Stage 4
	reg			aluZero4;
	reg	[31:0]	branchDst4, aluOut4, busB4;
	reg	[4:0]	rw4;
	wire[31:0]	memOut;
	
	//Stage 4 Control regs
	reg	memToReg4, regWrite4, memRead4, memWrite4, branch4;
	
	//Stage 5
	reg	[31:0]	memOut5, aluOut5;
	reg	[4:0]   rw5;
	wire [31:0]	regWriteData;
	
	//Stage 5 Control regs
	reg	memToReg5, regWrite5;

//============================================ Stage 1: IF Instruction Fetch ============================================//	
	assign jumpDecisionTarget = (jump & ~IFWrite) ? jumpTarget : currentPCPlus4;		//
	assign nextPC = (branch4 & aluZero4 & ~IFWrite) ? branchDst4 : jumpDecisionTarget;	//
		
	always @ (negedge CLK) begin
		if(~Reset_L)
			currentPC = startPC;
		else if(PCWrite)
			currentPC = nextPC;
	end
	assign currentPCPlus4 = currentPC + 4;
	InstrMem instrMemory(currentPC, currentInstruction);
//============================================Stage 2: ID Instruction Decode/register file read ============================================//	
	always @ (negedge CLK ) begin
		if(~Reset_L)
			currentInstruction2 = 32'b0;
		else if(IFWrite) begin
			currentInstruction2 = currentInstruction;
		end
	end

	assign {opcode, rs, rt, rd, shamt, func} = currentInstruction2;
	assign imm16 = currentInstruction2[15:0];
	
	//ADAPTED FROM SINGLECYCLE CONTROL: Need to implement bubble condition
	PipelinedControl controller(regDst, aluSrc, memToReg, regWrite, memRead, memWrite, branch, jump, signExtend, aluOp, RegClear, opcode, CLK, Bubble);

	//rw4 regdst3 rd3 rt3 move to execution stage
	assign #1 rw = regDst ? rd : rt;
	assign #2 UseShiftField = ( (aluOp == 4'b1100)||  (aluOp == 4'b1101)||  (aluOp == 4'b1001) );
	assign #2 rsUsed = (opcode != 6'b001111/*LUI*/) & ~UseShiftField;
	assign #1 rtUsed = (opcode == 6'b0) || branch || (opcode == 6'b101011/*SW*/);
	
	//Hazard Control
	Hazard hazard(PCWrite, IFWrite, Bubble, branch, aluZero4, jump,  regWrite ? rw : 5'b0  , rw3, rw4 ,rw5, rsUsed ? rs : 5'b0    , rtUsed ? rt : 5'b0 , Reset_L, CLK);  
	register_file  registers(rs, rt, rw5, regWriteData, RegClear, regWrite5, CLK, busA, busB);//modified	  
	extender immExt(signExtImm, imm16, signExtend);
	//get jump
	assign jumpTarget = {currentPC[31:28], currentInstruction2[25:0], 2'b00};
	
//stage3 after always block look at move
	assign #2 ALUImmRegChoice = aluSrc ? signExtImm : busB;
	assign #2 ALUAIn = UseShiftField ? busB : busA;
	assign #2 ALUBIn = UseShiftField ? {27'b0, shamt} : ALUImmRegChoice;
	
//============================================Stage 3: EX Execute and address calculation ============================================//
	always @ (negedge CLK ) begin
		if(~Reset_L || Bubble) begin
			ALUAIn3 <= 0;
			ALUBIn3 <= 0;
			busB3 <= 0;
			signExtImm3 <= 0;
			rw3 <= 0;
			rd3 <= 0;
			rt3 <= 0;
			regDst3 <= 0;
			memToReg3 <= 0;
			regWrite3 <= 0;
			memRead3 <= 0;
			memWrite3 <= 0;
			branch3 <= 0;
			aluOp3 <= 0;
			opcode3 <= 0;
		end
		/*else if(Bubble) begin
			ALUAIn3 <= 0;
			ALUBIn3 <= 0;
			busB3 <= 0;
			signExtImm3 <= 0;
			rw3 <= 0;
			regDst3 <= 0;
			memToReg3 <= 0;
			regWrite3 <= 0;
			memRead3 <= 0;
			memWrite3 <= 0;
			branch3 <= 0;
			aluOp3 <= 0;
		end*/
		else begin//  ID/EX register where control signals are stored
			ALUAIn3 <= ALUAIn;
			ALUBIn3 <= ALUBIn;
			busB3 <= busB;
			signExtImm3 <= signExtImm;
			rw3 <= rw;
			rd3 <= rd;
			rt3 <= rt;
			regDst3 <= regDst;
			memToReg3 <= memToReg;
			regWrite3 <= regWrite;
			memRead3 <= memRead;
			memWrite3 <= memWrite;
			branch3 <= branch;
			aluOp3 <= aluOp;
			opcode3 <= opcode;
		end
	end
	assign #1 rw4_temp = regDst3 ? rd3 : rt3;
	/*assign #2 ALUImmRegChoice = aluSrc ? signExtImm : busB;
	assign #2 ALUAIn = UseShiftField ? busB : busA;
	assign #2 ALUBIn = UseShiftField ? {27'b0, shamt} : ALUImmRegChoice;*/
	assign func3 = signExtImm3[5:0];
	//use opcode3
	ALUControl ALUCont(CLK, aluOp3, func/*, opcode3*/, aluCtrl);//ADAPTED from SingleCycleProc
	assign Carry_in =0;
	ALU_behav mainALU(CLK, ALUAIn3, ALUBIn3, Carry_in, aluCtrl, aluOut, Overflow, Carry_out, aluZero);//ADAPTED from SingleCycleProc

	always@(posedge CLK) begin
	$display($time, "Within the Proc: aluOut=%d",aluOut);
	end
	assign shiftedSignExtImm = {signExtImm3[29:0], 2'b0};
	//branchdst4 currentpc3
	//assign branchDst = currentPCPlus4 + shiftedSignExtImm;

//============================================Stage 4: MEM Memory Access ============================================//		
	always @ (negedge CLK ) begin
		if(~Reset_L) begin
			aluZero4 <= 0;
			branchDst4 <= 0;
			aluOut4 <= 0;
			busB4 <= 0;
			rw4 <= 0;
			memToReg4 <= 0;
			regWrite4 <= 0;
			memRead4 <= 0;
			memWrite4 <= 0;
			branch4 <= 0;
		end
		else begin//  EX/MEM register
			aluZero4 <= aluZero;
			branchDst4 <= currentPCPlus4 + shiftedSignExtImm;
			aluOut4 <= aluOut;
			busB4 <= busB3;
			rw4 <= rw4_temp;
			memToReg4 <= memToReg3;
			regWrite4 <= regWrite3;
			memRead4 <= memRead3;
			memWrite4 <= memWrite3;
			branch4 <= branch3;
		end
	end
	DataMem data(aluOut4, CLK , memRead4, memWrite4, busB4, memOut);//ADAPTED from SingleCycleProc
	assign dMemOut = memOut;
	
//============================================Stage 5: WB Write Back ============================================//		
	always @ (negedge CLK ) begin
		if(~Reset_L) begin
			memOut5 <= 0;
			aluOut5 <= 0;
			rw5 <= 0;
			memToReg5 <= 0;
			regWrite5 <= 0;
		end
		else begin//  MEM/WB register
			memOut5 <= memOut;
			aluOut5 <= aluOut4;
			rw5 <= rw4;
			memToReg5 <= memToReg4;
			regWrite5 <= regWrite4;
		end
	end
	assign #1 regWriteData = memToReg5 ? memOut5 : aluOut5;

	always @(posedge CLK)
	#10 $display($time,
	"\n================================ REG 1 (IF/ID) ================================\ncurPC=%d Instr: op=%b rs=%d rt=%d rw=%d imm16=%d funct=%d signExtImm=%d",
	currentPC,currentInstruction2[31:26],rs, rt, rw, currentInstruction2[15:0], currentInstruction2[5:0],signExtImm,

	"\n================================ REG 2 (ID/EX) ================================\nALUAIn3=%d ALUBIn3=%d aluOut=%d busB3=%d signExtImm3=%d aluOp3=%b aluCtrl=%b func3=%b  rw3=%d rd3=%d rt3=%d opcode3=%b ALUAIn=%d ALUBIn=%d",
	ALUAIn3,ALUBIn3,aluOut,busB3, signExtImm3,aluOp3,aluCtrl,func3,rw3,rd3,rt3,opcode3,ALUAIn,ALUBIn,

	"\n================================ REG 3 (EX/MEM) ================================\nALUResult4=%d branchDst4=%d busB4=%d rw4=%d" ,
	aluOut4,branchDst4,busB4,rw4,

	"\n================================ REG 4 (MEM/WB) ================================\nALUResult5=%d rw5=%d regWriteData=%d",
	aluOut5,rw5,regWriteData,"\n");
  	
endmodule

//============================================Test Module ============================================//		
module m555 (CLK);
   parameter StartTime = 0, Ton = 50, Toff = 50, Tcc = Ton+Toff; // 
 
   output CLK;
   reg 	  CLK;
   
   initial begin
      #StartTime CLK = 0;
   end
   
   // The following is correct if clock starts at LOW level at StartTime //
   always begin
      #Toff CLK = ~CLK;
      #Ton CLK = ~CLK;
   end
endmodule

   
module testCPU(Reset_L, startPC, testData);
   input 	[31:0] testData;
   output 	Reset_L;
   output 	[31:0] startPC;
   reg 	 	Reset_L;
   reg 		[31:0] 	 startPC;
   
   initial begin
      // Your program 1
    /*  Reset_L = 0;  startPC = 0 * 4;
	  
      #101 // insures reset is asserted across negative clock edge
	  Reset_L = 1; 
	  
      #10000; // allow enough time for program 1 to run to completion
      Reset_L = 0;
      #1 $display ("\nProgram 1: Result: %d \n", testData);*/
      
      // Your program 2
      Reset_L = 0; startPC = 14 * 4;
      #101 
	  Reset_L = 1; 
      #10000;
      Reset_L = 0;
      #1 $display ("\nProgram 2: Result: %d\n", testData);

      $finish;
   end
endmodule // testCPU

module TopProcessor;
   wire reset, CLK, Reset_L;
   wire [31:0] startPC;
   wire [31:0] testData;
   
   m555 system_clock(CLK);
   PipelinedProc SSProc(CLK, Reset_L, startPC, testData);
   testCPU tcpu(Reset_L, startPC, testData); 

endmodule // TopProcessor
